﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Bonehead
{
    public class ParsedThread
    {
        private long mID;
        private ParsedProfile mOwner;
        private Stack<ParsedCallstack> mCallstack = new Stack<ParsedCallstack>();
        private Stack<ParsedMethod> mUnwindStack = new Stack<ParsedMethod>();
        private Stack<long> mCycleStack = new Stack<long>();
        private Dictionary<long, ParsedMethod> mMethods = new Dictionary<long, ParsedMethod>();
        private ParsedCallstack mRoot = new ParsedCallstack(null);

        public ParsedThread(long aID, ParsedProfile aOwner)
        {
            mID = aID;
            mOwner = aOwner;
            mCallstack.Push(mRoot);
            mRoot.AddCycles(0);
        }

        public long ID
        {
            get { return mID; }
        }

        public ParsedCallstack Root
        {
            get { return mRoot; }
        }

        public IEnumerable<ParsedMethod> Methods
        {
            get { return mMethods.Values; }
        }

        public void Sample(Profile.Sample aSample)
        {
            switch (aSample.Type)
            {
                case Profile.SampleType.Enter:
                    Enter(aSample);
                    break;

                case Profile.SampleType.Leave:
                    Leave(aSample);
                    break;

                case Profile.SampleType.Tail:
                    Leave(aSample);
                    break;

                case Profile.SampleType.ManagedToUnmanagedTransition:
                    mCallstack.Peek().AddTransition();
                    break;

                case Profile.SampleType.UnmanagedToManagedTransition:
                    mCallstack.Peek().AddTransition();
                    break;

                case Profile.SampleType.ExceptionUnwindFunctionEnter:
                    UnwindEnter(aSample);
                    break;

                case Profile.SampleType.ExceptionUnwindFunctionLeave:
                    UnwindLeave(aSample);
                    break;

                case Profile.SampleType.ExceptionThrown:
                    mCallstack.Peek().AddException();
                    break;

                default:
                    System.Diagnostics.Debug.Print("{0} {1}", aSample.Type, aSample.Function);
                    break;
            }
        }

        private void Enter(Profile.Sample aSample)
        {
            ParsedCallstack caller = mCallstack.Peek();

            // lookup method we're calling.
            ParsedMethod method;
            if (!mMethods.TryGetValue(aSample.Function, out method))
            {
                method = new ParsedMethod(aSample.Function, mOwner.GetMethodName(aSample.Function));
                mMethods.Add(aSample.Function, method);
            }

            // push on callstack.
            mCallstack.Push(caller.Add(method));
            mCycleStack.Push(aSample.Cycle);
        }

        private void Leave(Profile.Sample aSample)
        {
            ParsedMethod leave = mMethods[aSample.Function];
            if (!Object.ReferenceEquals(leave, mCallstack.Peek().Method))
            {
                throw new Exception("huh?");
            }
            PopCallstack(aSample);
        }

        private void UnwindEnter(Profile.Sample aSample)
        {
            mUnwindStack.Push(mMethods[aSample.Function]);
        }

        private void UnwindLeave(Profile.Sample aSample)
        {
            ParsedMethod unwind = mUnwindStack.Pop();
            if (!Object.ReferenceEquals(unwind, mCallstack.Peek().Method))
            {
                throw new Exception("huh?");
            }
            PopCallstack(aSample);          
        }

        private void PopCallstack(Profile.Sample aSample)
        {
            // add cycle count.
            long cycles = (aSample.Cycle - mCycleStack.Pop());
            ParsedCallstack call = mCallstack.Pop();
            call.AddCycles(cycles);
            call.AddCall();

            ParsedMethod caller = mCallstack.Peek().Method;
            if (caller != null)
            {
                call.Method.AddCaller(caller);
                caller.AddCallee(call.Method, cycles);
            }
        }

        public override string ToString()
        {
            return String.Format("Thread [0x{0}]", ID);
        }
    }
}
